RxJS の言葉で Recoil を説明する
Recoil がやっているのは Hot Observable と React Hooks を使った状態管理 ならば rxjs を知っている人は簡単に理解できるのでは? 同じ所と違うところを考えてみる
Atom は Subject とよく似ている
1つの Atom は1種類のデータを持ち、外側から変更を加えたり、購読したりできる
基本的に利用されるのは最新の値だけで、古い値はどんどん捨てられる
値そのものは、どちらも同じように Immutable に扱われるべきである
値を取り出す時は、どちらも同じように購読するが、Recoil では React Hooks によって隠蔽されるので、見かけ上は最新の値を直ちに取り出せるかのように見える
e.g. const value = useRecoilValue(atom);
これは最新の値を参照しているのではなく、この Component の中で Atom を購読している
新しい値が流れてきた時は上位の Component 関数が再実行されるので、この行も再度呼ばれることになり、新しい値が使える
少し違うのは、Atom そのものはデータの参照であり、実態は React Context ごとに作られるということ
そのため new Subject した時点で値を取り出せる RxJS と違って、Recoil ではマウントされるまで値が存在せず、コンポーネントから Hooks を通じて値を取り出すしかない
<RecoilRoot> が1つしかないプロジェクトであれば、Atom とデータは1:1で対応する
ReadOnlySelector は merge と map の組み合わせによく似ている
data$.pipe(...) で新しいストリームを作るように、既存の Atom や Selector から、新しいストリーム(のようなもの)を定義できる
RxJS で merge(a$, b$) が出来るように、複数のデータソースをまとめて参照できる
RxJS で map(a => a + 1) が出来るように、値を加工して新しい値にすることができる
この操作が selector と名付けられた所以なのだと思う teramotodaiki.icon
新しい値が上流から流れてきたら、ある関数が実行されて、値が更新されるという点では、どちらも変わらない
ただし、インターフェースは大きく異なる
RxJS の pipable は、Observable<T> => Observable<S> な関数を複数登録すると、それらが順番に実行されて、最後に出てきたストリームから値を取り出せる
Recoil の Selector は、データの購読・加工・次の値の決定をひとつの get 関数で行う。
get() という特殊なヘルパー関数(Selector に指定する get とは別物)を使うと、 get 関数の中でデータを購読できる。get() ヘルパーは、まさに React Hooks と同じ性質を持っていて、新しい値がやってきたら、外側の関数を再実行させる。そのため、見かけ上は同期的に値を取り出しているかのように見える
加工は map に指定するコールバックと同じように、get関数の中で処理を行う
次の値は map と同じように、get 関数の中で return された値が次の値になる
code:compare.js
// RxJS
const hoge$ = fuga$.pipe(
map(count => count + 1)
);
// Recoil (fuga は Atom)
const hoge = selector({
key: 'hoge',
get: ({ get }) => {
const count = get(fuga);
return count + 1;
}
});
この比較を見ても同じには見えないかも知れないけど、やってる事は本質的に等価です
ReadWriteSelector は…
罪深いからはじめは知らなくていいと思う(個人の見解)。
ReadOnlySelector に set関数 を足したもので、そのパラメータとして get() set() reset() という3つのヘルパーが与えられる
get() はその時点の値をスナップショット的に取得する
set() は任意の Atom (or ReadWriteSelector) に新しい値を設定する
reset() は単なる set(atom, new DefaultValue()) のシノニム
JavaScript の Property Descriptor の setter をイメージしてもらうと分かりやすい
あくまで代入の操作をハンドルできるもので、自身の Selector が持つ値とは独立している
自身の Selector が持つ値とは、 get 関数が最後に返したもの(ReadOnlySelector と同じ)